﻿/*
 * Copyright 2011 Edward L. Stauff.                           Contact: <EdStauff@gmail.com>.
 * 
 * This file, "XerialTypeMapping.cs", is part of the "Stauffware.Common.Xml.Xerialization" 
 * library.  "Stauffware.Common.Xml.Xerialization" is free software: you can redistribute 
 * it and/or modify it under the terms of the GNU Lesser Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or (at your option) 
 * any later version.
 * 
 * "Stauffware.Common.Xml.Xerialization" is distributed in the hope that it will be 
 * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser Public License for more 
 * details.
 * 
 * You should have received a copy of the GNU Lesser Public License along with 
 * "Stauffware.Common.Xml.Xerialization".  If not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using Stauffware.Common.Core;

namespace Stauffware.Common.Xml.Xerialization
{
	/// <summary>
	/// Base class for classes that provide a mapping between .Net types and XML.
	/// </summary>
	/// <remarks>
	/// At least one of AllowsXmlElmnt, AllowsXmlAttr and AllowsXmlText will always
	/// be true (or TypeMappingDictionary won't accept it).
	/// </remarks>

	public abstract class XerialTypeMapping : XerialMapping
	{
		/// <summary>
		/// The type handled by this mapping.  This will never be null.
		/// </summary>
		public readonly Type TargetType;

		/// <summary>
		/// The default XML type (attribute or element) for this data type.
		/// </summary>
		public readonly XmlType DefaultXmlType;

		/// <summary>
		/// Returns whether this class supports mapping to XML elements.
		/// </summary>
		public abstract bool AllowsXmlElmnt { get; }

		/// <summary>
		/// Returns whether this class supports mapping to XML attributes.
		/// </summary>
		public abstract bool AllowsXmlAttr { get; }

		/// <summary>
		/// Returns whether this class supports mapping to XML text (entire body of an element).
		/// </summary>
		public abstract bool AllowsXmlText { get; }

		/// <summary>
		/// The XerialContext to which this mapping belongs.  This will never be null.
		/// </summary>
		protected override XerialContext Context { get { return this._context; } }
		private readonly XerialContext _context;

		/// <summary>
		/// Is this an internally defined type?  (For debugging and dumping.)
		/// </summary>
		internal bool IsInternal = false;

		//-----------------------------------------------------------------------------
		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="context">
		///		the context to which this mapping will belong (must not be null)
		///	</param>
		///	<param name="targetType">
		///		the type to be handled by this mapping (must not be null)
		///	</param>
		/// <param name="xmlName">
		///		the default name to use for the XML element or attribute (must not be null)
		/// </param>
		/// <param name="defaultXmlType">
		///		The default XML type (attribute or element) for this data type.
		/// </param>

		protected XerialTypeMapping (XerialContext context, Type targetType, string xmlName, 
																	XmlType defaultXmlType)
			: base(xmlName == null ? CreateDefaultXmlName(targetType) : xmlName)
		{
			Require.NotNull(context);
			Require.NotNull(targetType);

			this._context = context;
			this.TargetType = targetType;
			this.DefaultXmlType = defaultXmlType;
		}

		//-----------------------------------------------------------------------------
		/// <summary> Returns the default XML name for the given type.  </summary>

		protected static string CreateDefaultXmlName (Type type)
		{
			string name = type.Name.Replace("`", "");

			if (type.IsGenericType || type.IsGenericTypeDefinition)
			{
				string gArgs = "";
				foreach (Type gt in type.GetGenericArguments())
					gArgs += iUmlaut + CreateDefaultXmlName(gt);
				name += iAcute + gArgs.Substring(1) + iGrave;
			}

			return name;
		}

		// chosen for their appearance
		private const string iAcute = "\xED";
		private const string iGrave = "\xEC";
		private const string iUmlaut = "\xEF";

		//-----------------------------------------------------------------------------

		/// <summary>
		/// Writes an object to XML as one complete XML element (which may include
		/// attributes, child elements, and/or text).
		/// </summary>
		/// <param name="xmlName">
		///		The name to use for the element.  
		///		If this parameter is null, then this object's XmlName is used instead.
		/// </param>
		/// <param name="targetObj">The object to be written to XML (can be null)</param>
		/// <param name="writer">Where to write the XML.</param>
		public virtual void WriteToXmlElmnt (string xmlName, object targetObj, XmlWriter writer)
		{
			throw new XerialException(this.TargetType.FullName + 
								" does not support xerialization to an XML element");
		}

		/// <summary>
		/// Writes an object to XML as one XML attribute.
		/// </summary>
		/// <param name="xmlName">
		///		The name to use for the attribute.  
		///		If this parameter is null, then this object's XmlName is used instead.
		/// </param>
		/// <param name="targetObj">the object to be written to XML (can be null)</param>
		/// <param name="writer">Where to write the XML.</param>
		public virtual void WriteToXmlAttr (string xmlName, object targetObj, XmlWriter writer)
		{
			throw new XerialException(this.TargetType.FullName + 
								" does not support xerialization to an XML attribute");
		}
		
		/// <summary>
		/// Writes an object to XML as text.
		/// </summary>
		/// <param name="targetObj">the object to be written to XML (can be null)</param>
		/// <param name="writer">Where to write the XML.</param>

		public virtual void WriteToXmlText (object targetObj, XmlWriter writer)
		{
			throw new XerialException(this.TargetType.FullName + 
								" does not support xerialization to XML text");
		}

		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

		/// <summary>
		/// Reads an object from one complete XML element (which may include attributes, 
		/// child elements, and/or text).
		/// </summary>
		/// <param name="reader">
		///		Where to read the XML from.
		///		On entry, the current node must be an Element.
		///		On exit, the current node will be the node immediately following the Element
		///			(for an empty Element) or the corresponding EndElement (for a non-empty element).
		/// </param>
		/// <returns>the newly created object</returns>
		public virtual object ReadFromXmlElmnt (NiceXmlReader reader)
		{
			throw new XerialException(reader, this.TargetType.FullName + 
								" does not support xerialization to an XML element");
		}

		/// <summary>
		/// Reads an object from one XML attribute.
		/// </summary>
		/// <param name="reader">
		///		Where to read the XML from.
		///		On entry, the current node must be either an Element or an Attribute.
		///		On exit, the current node will remain the same.
		/// </param>
		/// <param name="name">the name of the XML attribute to be read</param>
		/// <returns>the newly created object</returns>
		public virtual object ReadFromXmlAttr (NiceXmlReader reader, string name)
		{
			throw new XerialException(reader, this.TargetType.FullName + 
								" does not support xerialization to an XML attribute");
		}

		/// <summary>
		/// Reads an object from XML text.
		/// </summary>
		/// <param name="reader">
		///		Where to read the XML from.
		///		On entry, the current node must be at the beginning of the text content to be read.
		///		On exit, the current node will be the first node not read.
		/// </param>
		/// <returns>the newly created object</returns>
		public virtual object ReadFromXmlText (NiceXmlReader reader)
		{
			throw new XerialException(reader, this.TargetType.FullName + 
								" does not support xerialization to XML text");
		}

		//- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		/// <summary>
		/// Returns whether this.TargetType is or derives from that.TargetType.
		/// </summary>

		public virtual bool IsOrDerivesFrom (XerialTypeMapping that)
		{
			return this == that;
		}

		//-----------------------------------------------------------------------------
		/// <summary> Dumps its guts.  For debugging only. </summary>

		public virtual void Dump (System.IO.TextWriter tw)
		{
			string indent = "    ";

			string className = this.GetType().Name;
			if (className.StartsWith("Xerial")) className = className.Substring(6);

			tw.WriteLine(" <" + this.XmlName + "> " + 
						Chars.LeftArrow.Unicode.ToString() + Chars.RightArrow.Unicode.ToString() +
						" " + this.TargetType.FullName + " (" + className + ")");

			tw.WriteLine(indent + "allowed XML types: Element= " + DumpXmlType(XmlType.Element) +
									", Attribute= " + DumpXmlType(XmlType.Attribute) +
									", Text= " + DumpXmlType(XmlType.Text));
		}

		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

		private string DumpXmlType (XmlType t)
		{
			if (this.DefaultXmlType == t)
				return "default";
			if (t == XmlType.Attribute) return this.AllowsXmlAttr ? "yes" : "no";
			if (t == XmlType.Element) return this.AllowsXmlElmnt ? "yes" : "no";
			if (t == XmlType.Text) return this.AllowsXmlText ? "yes" : "no";
			Assert.NotReached();
			return "";
		}

		//-----------------------------------------------------------------------------
	}
}
